package dnl.jexem.prcs;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import dnl.jexem.cmd.CommandExecution;
import dnl.jexem.cmd.CommandExecutionFactory;

public class JpsProcessOperations {

    private PatternMatcher full = new FullLinePatternMatcher();
    private PatternMatcher part = new PartLinePatternMatcher();

    /**
     * Gets info for Java processes running on this machine. This method parses
     * the output of the jps command and currently returns
     * <code>JavaProcessInfo</code> that have only a processID, main/jar
     * attribute, and arguments.
     *
     * @return
     */
    public List<JavaProcessInfo> getJavaProcesses() {
        CommandExecution commandExecution = CommandExecutionFactory.createLocalCommandExecution("jps", "-lm");
        commandExecution.executeCommand(1000);
        if (commandExecution.getExitStatus() != 0) {
            System.err.println(commandExecution.describeWithErrors());
            return Collections.emptyList();
        }
        String output = commandExecution.getOutput();
        List<JavaProcessInfo> processInfos = new ArrayList<JavaProcessInfo>();
        String[] lines = StringUtils.split(output, "\n\r");
        for (String line : lines) {
            JavaProcessInfo processInfo = parseJpsLine(line);
            // add anything that is not the excecution of jps
            if (processInfo != null
                    && !processInfo.getMainOrJar().endsWith(".Jps")) {
                processInfos.add(processInfo);
            }
        }

        return processInfos;
    }

    /**
     * Uses the given expression to resolve Java processes.
     *
     * @param grepExpression
     * @return
     */
    public List<JavaProcessInfo> grepJavaProcesses(String grepExpression) {
        List<JavaProcessInfo> result = new ArrayList<JavaProcessInfo>();
        List<JavaProcessInfo> javaProcesses = getJavaProcesses();
        for (JavaProcessInfo javaProcessInfo : javaProcesses) {
            if (javaProcessInfo.getMainOrJar().indexOf(grepExpression) >= 0) {
                result.add(javaProcessInfo);
            } else if (javaProcessInfo.getArguments() != null
                    && javaProcessInfo.getArguments().indexOf(grepExpression) >= 0) {
                result.add(javaProcessInfo);
            }
        }
        return result;
    }

    private JavaProcessInfo parseJpsLine(String line) {
    	JavaProcessInfo pi = full.match(line);
    	if(pi == null){
    		pi = part.match(line);
    	}
    	if(pi == null){
    		System.err.println("Could not match '"+line+"'");
    	}
        return pi;
    }
    
    private class FullLinePatternMatcher implements PatternMatcher {

    	private Pattern pattern = Pattern.compile("(\\d+)\\s{1}([\\w|/|\\\\|:|.]+)\\s{1}(.*)");
    	
		@Override
		public JavaProcessInfo match(String line) {
			Matcher matcher = pattern.matcher(line);
	        if (matcher.matches()) {
	            JavaProcessInfo pi = new JavaProcessInfo();
	            pi.setProcessID(Integer.parseInt(matcher.group(1)));
	            pi.setMainOrJar(matcher.group(2));
	            pi.setArguments(matcher.group(3));
	            return pi;
	        } else {
	            return null;
	        }
		}
    }
    
    private class PartLinePatternMatcher implements PatternMatcher {

    	private Pattern pattern = Pattern.compile("(\\d+)\\s{1}([\\w|/|\\\\|:|.]+)");
    	
		@Override
		public JavaProcessInfo match(String line) {
			Matcher matcher = pattern.matcher(line);
	        if (matcher.matches()) {
	            JavaProcessInfo pi = new JavaProcessInfo();
	            pi.setProcessID(Integer.parseInt(matcher.group(1)));
	            pi.setMainOrJar(matcher.group(2));
	            return pi;
	        } else {
	            return null;
	        }
		}
    }
    
    private static interface PatternMatcher {
    	abstract JavaProcessInfo match(String s);
    }
}
